/*
 *  Copyright (C) 2003 MediaRush, Inc.
 *
 *  This software is copyrighted to MediaRush, Inc. It cannot
 *  be distributed or modified without the express written 
 *  approval of MediaRush, Inc.
 * 
 *  postmaster@mediarush.com
 * 
 *  File: GC100Driver.java
 *
 */

package com.cidero.bridge.gc100;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.net.URL;

import com.cidero.util.SynchronizedQueue;

/**
 * Driver class for single instance of a GC100 device. Each device controls 
 * one or more 'sub-devices' via IR/serial/relays.  
 * on TCP port 2253 on the Prismiq box. Commands/responses are simple
 * text strings sent over TCP port 4998
 *
 * @author <a href="mailto:newell@mediarush.com"></a>
 * @version 1.0
 *
 *
 * Most GC100 errors are handled by a return of 'unknowncommand' with a code,
 * e.g.:
 *
 *  'unknowncommand 14'
 *
 * The codes are:
 *
 *   1  Time out occurred because <CR> was not received
 *   2  Invalid module address for getversion command
 *   3  Invalid module address for other commands?
 *   4  Connector address is not valid
 *   5  Connector address 1 is set as "sensor in" when attempting to send IR
 *   6  Connector address 2 is set as "sensor in" when attempting to send IR
 *   7  Connector address 3 is set as "sensor in" when attempting to send IR
 *   8  Offset set to even number for IR command
 *   9  Max number of transitions (IR) exceeded. (256 on/off total allowed)
 *  10  Number of transitions (IR) not even
 *  11  Contact closure command sent to module that is not a relay
 *  12  Undefined 
 *  13  State requested of an invalid connector
 *  14  Unsupported command
 *
 */
//public class GC100Driver extends UPNPBridgeDriver
public class GC100Driver
{
  private final static int GC100_COMMAND_PORT = 4998;
  private final static int RESPONSE_QUEUE_SIZE = 50; 

  String                 host;
  int                    port = GC100_COMMAND_PORT;
  Socket                 socket = null;
  GC100ReadThread readThread = null;
  SynchronizedQueue      responseQueue;
  int                    volumePercent = 50;
  int                    seqNum = 0;
  URL                    url = null;   // Current URL
  
  //public GC100Driver( UPNPBridge parentBridge )
  public GC100Driver()
  {
    //super( parentBridge );
    
    //
    // Scan all addresses in same group as host (simple algorithm
    // for now - just check for hosts with port 2253 open )
    //

    // temp hardcoded - add discovery
    host = "192.168.1.70"; 
    //host = "192.168.0.45"; 

    connect();
  }
  
  //public GC100Driver( UPNPBridge parentBridge, String host, int port )
  public GC100Driver( String host, int port )
  {
    //super( parentBridge );

    this.host = host;
    this.port = port;

    connect();
  }
  

  public void connect()
  {
    System.out.println("Connecting to GC100...");

    if( socket == null )
    {
      try
      {
        socket = new Socket( host, port );

        //
        // Create separate reader thread to read back responses from 
        // PRISM device (so blocking read won't hang main thread 
        // if something goes wrong)
        //
        if( readThread != null )  // Stop existing thread
          readThread.stop();

        responseQueue = new SynchronizedQueue( RESPONSE_QUEUE_SIZE );

        GC100ReadThread readThread = 
          new GC100ReadThread( socket, responseQueue );

        readThread.start();

      }
      catch( Exception e )
      {
        System.out.println( "Error connecting to GC100 on port " + port );
        socket = null;
        return;
      }
    }
    
    System.out.println("Connected ok");

    init();

  }

  public String getUUID() 
  {
    return "GC100@" + host;
  }
  
  //
  // Read back GC-100 device configuration. The GC100 is a flexible
  // device and can have different cards (IR/serial/relay) in different
  // slots
  //

  public void init()
  {

    sendMsg( "getdevices");

    try { Thread.sleep(1000); } catch ( Exception e ) {}

    sendMsg( "getversion,1" );

    try { Thread.sleep(1000); } catch ( Exception e ) {}

    sendMsg( "getversion,2" );
    
  }
  
  public void open()
  {
  }
  public void close()
  {
  }

  /**
   *  Send an IR command to the specified GC100 port address
   *
   *  @param  irPortAddr    2:1, 2:2, or 2:3 for vanilla GC-100-6
   *  @param  cmdId         16-bit id used to tag responses
   *  @param  freq          Frequency of carrier (Hz)
   *  @param  count         Repeat count for command
   *  @param  offset        If count is > 1, this number indicates the offset
   *                        within the timing pattern to start repeating the
   *                        IR command. Offset will always be odd since a 
   *                        timing pattern begins with an <on> state
   *
   *  @param  onOffPattern  On/Off pattern. The pattern values are measured
   *                        in periods of the carrier frequency (1-65536)
   *
   *                        There must be an equal number of on/off states.
   *                        Every on/off state must meet an 80uS minimum 
   *                        time requirement for the GC100 to work properly.
   *                        For a carrier freq of 48 KHz, the min value 
   *                        is 80us*48KHz = 3.84. For proper GC100 operation,
   *                        all on/off values in the timing pattern must be 
   *                        4 or higher
   *
   */

  public boolean sendIR( String irPortAddr, int cmdId )
  {
    return true;
  }
  

  public void play( String speed )
  {
    //Thread.sleep(1000);
  }

  public void pause()
  {
  }

  public void stop()
  {
  }

  public void setVolume( String upnpVolume )
  {
  }
  public String getVolume()
  {
    return "100";
  }

  public void setMute( String muteFlag )
  {
  }

  public String getMute()
  {
    return "";
  }
  
  public void setTransportURI( String uri )
  {
  }

  /**
   * send a message to the prismiq socket. Messages are preceded by an
   * id string which is included as part of the response. This allows
   * the sender to check for errors due to missed responses, etc...
   */ 
  public void sendMsg( String msg )
  {
    if( socket == null )
      connect();

    if( socket == null )
    {
      System.out.println("sendMsg: Couldn't connect");
      return;
    }
      
    seqNum = ((seqNum+1) % 100);
    
    // Read all pending queue entries (flush response queue)
    
    System.out.println("sendMsg: Flushing input queue");
    while( !responseQueue.isEmpty() )
    {
      String response = (String)responseQueue.get(1);
      System.out.println("Flushing response: " + response );
    }

    try 
    {
      OutputStream out = socket.getOutputStream();
      PrintStream pout = new PrintStream( out );
      pout.print( msg + "\r");
      pout.flush();
    }
    catch( Exception e )
    {
      System.out.println( e );
    }
    
    System.out.print( "Sent msg: " + msg );

    //
    // Wait for response with matching seqNum. If none found after 4 sec,
    // punt
    //
    String response;
    
    response = (String)responseQueue.get(4000);

    if( response == null )
    {
      System.out.println("Timeout waiting for matching response");
    }
    else
    {
      System.out.println("response from queue: " + response );
    }
    
  }

  //
  // Test code
  //
  public static void main( String args[] )
  {
    GC100Driver driver = new GC100Driver();
    
    BufferedReader reader = 
      new BufferedReader( new InputStreamReader(System.in) );

    try 
    {
      
      while( true )
      {
        System.out.println("Enter command: ");
        String cmd = reader.readLine();

        if( cmd.length() < 4 )
        {
          System.out.println("Sending Sony TV on/off");
          
          cmd = "sendir,2:1,1,40000,2,1,96,24,48,24,24,24,48,24,24,24,48,24,24,24,24,24,48,24,24,24,24,24,24,24,24,1032";
          
          System.out.println("Cmd is: " + cmd );
          driver.sendMsg( cmd );
        }
        else
        {
          System.out.println("cmd is: " + cmd );
          driver.sendMsg( cmd );
        }
      }
    }
    catch( Exception e )
    {
      System.out.println( e );
      System.exit(-1);
    }
    
  }
  
}
